/*
 * Functions for read(recv) and write(send) from sockets
 */
#ifndef SOCK_READ_WRITE_H_
#define SOCK_READ_WRITE_H_

#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

// TODO 要区分连接中断的情况，调用者才能正确的关闭链接

/*
 * @brief	带超时的数据接收
 * @param	fd: 网络套接字
 * 		buffer: 用来存放数据的buffer
 * 		len: buffer处可用的空间大小
 * 		nsec: 超时限制，单位秒，0表示不设置超时
 * @return	接收并存放到buffer处的数据长度， -1表示出错， 0表示超时
 *
 */
static inline int recv_with_timedout(int fd, char *buffer, size_t len, int nsec)
{
	if (fd < 0 || buffer == NULL || len <= 0 || nsec < 0)
		return -1;

	struct timeval tv = { nsec, 0 };

	fd_set set;
	FD_ZERO(&set);
	FD_SET(fd, &set);

	int ret = select(fd + 1, &set, NULL, NULL, &tv);
	if (ret < 0) {
		perror("select failed: ");
		return -1;
	} else if (ret == 0) {
		printf("no data within %d secs\n", nsec);
		return 0;
	}

	int size = -1;
	size = recv(fd, buffer, len, MSG_DONTWAIT); /* noneblock */
	if (size < 0)
		perror("recv failed.");

	return size;
}

/*
 * @brief	发送n个字节，直到发送完或者超时
 * @param	fd：网络套接字
 * 		buff和size：需要发送的数据所在位置和大小
 * 		timeout：超时设置，单位为毫秒（千分之一秒）
 * @return	发送成功返回n，发送失败返回-1，发送超时返回0
 */
static inline int send_nbytes(int fd, const char *buf, size_t n, int timeout)
{
	const int CPU_YEILD = 1; /* ms */
	size_t offset = 0;
	size_t needto_send = n;
	int count = timeout / CPU_YEILD;
	// printf("total %d bytes.\n", n);
	while (needto_send) {
		int done = send(fd, buf + offset, needto_send, MSG_DONTWAIT);
		if (done < 0) {
			if (errno == EWOULDBLOCK) {
				//printf("EWOULDBLOCK\n");
				;/* nop */
			} else {
				printf("send n bytes error, errno = %d, %s",
				errno, strerror(errno));
				return -1;
			}
		} else {
			needto_send -= done;
			offset += done;
//			printf("%d bytes send, %d bytes left.\n", offset,
//					needto_send);
		}
		usleep(CPU_YEILD * 1000);
		if (timeout > 0) {
			if (--count <= 0) {
				printf("send n bytes timeout.\n");
				return 0;
			}
			/* TODO 返回已经发送出去的数量? */
		}
	}
	return n;
}

/*
 * @brief	接收n个字节，直到接收完成或出错
 * @param	fd: 网络套接字
 * 		buf: 用于存放接收数据的buffer,调用者保证buf处可用空间至少为n
 * 		n: 需要接收的数据大小
 * @return	0表示接收完成, -1表示出错
 */
static inline int recv_nbytes(int fd, char *buf, size_t n)
{
	size_t offset = 0;
	size_t read_more = n;

	while (read_more) {
		int done = recv_with_timedout(fd, buf + offset, read_more, 0);
		if (done <= 0) {
			return -1;
		} else {
			read_more -= done;
			offset += done;
		}
	}

	return 0;
}

#endif /* SOCK_READ_WRITE_H_ */
